Skip to content

feat(profiling): real torch flame graphs - #18457

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 4 commits into
mainfrom
kowalski/feat-profiling-real-torch-flame-graphs
Jun 12, 2026
Merged

feat(profiling): real torch flame graphs#18457
gh-worker-dd-mergequeue-cf854d[bot] merged 4 commits into
mainfrom
kowalski/feat-profiling-real-torch-flame-graphs

Conversation

@KowalskiThomas

@KowalskiThomas KowalskiThomas commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR makes our PyTorch integration create profiles that actually look like profiles. Previously, we would get events from the PyTorch Profiler, sample them, and report them without looking at their context. As a result, we would get a "flat flame graph" where every frame would be at the same level.

Results

Before my changes flame graphs are "flat"

image

After my changes flame graphs are flame graphs (note: different code being profiled)

image

Live example profile (while stocks retention lasts!)

Details

The previous flame graph was obtained by profiling the following script.

import os
from time import time

import torch
from torch import nn
from torch.profiler import ProfilerActivity


class FeedForward(nn.Module):
    def __init__(self, d_model: int, d_ff: int) -> None:
        super().__init__()
        self.w1 = nn.Linear(d_model, d_ff)
        self.act = nn.GELU()
        self.w2 = nn.Linear(d_ff, d_model)
        self.norm = nn.LayerNorm(d_model)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.norm(x + self.w2(self.act(self.w1(x))))


class TransformerBlock(nn.Module):
    def __init__(self, d_model: int, n_heads: int, d_ff: int) -> None:
        super().__init__()
        self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
        self.attn_norm = nn.LayerNorm(d_model)
        self.ff = FeedForward(d_model, d_ff)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        attn_out, _ = self.attn(x, x, x)
        x = self.attn_norm(x + attn_out)
        return self.ff(x)


class DeepModel(nn.Module):
    def __init__(self, d_model: int = 256, n_heads: int = 4, d_ff: int = 512, n_layers: int = 6) -> None:
        super().__init__()
        self.embed = nn.Linear(64, d_model)
        self.blocks = nn.ModuleList([TransformerBlock(d_model, n_heads, d_ff) for _ in range(n_layers)])
        self.head = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.GELU(),
            nn.Linear(d_ff, d_model),
            nn.LayerNorm(d_model),
            nn.Linear(d_model, 16),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.embed(x)
        for block in self.blocks:
            x = block(x)
        return self.head(x)


def main() -> None:
    torch.set_num_threads(1)

    model = DeepModel()
    x = torch.randn(8, 32, 64)  # (batch, seq_len, input_dim)

    execution_time_sec = float(os.getenv("EXECUTION_TIME_SEC", "30"))
    end = time() + execution_time_sec
    with torch.no_grad():
        while time() < end:
            with torch.profiler.profile(activities=[ProfilerActivity.CPU]):
                for _ in range(10):
                    model(x)


main()

Performance

Note this theoretically comes at a performance cost, as we need to reconstruct the event parent tree. However, looking at a Python profile for the Profiler (convenient that our Profiler is written in Python!)
The top lines are

  1. events = prof.events() -- this line hasn't changed and used to be called as much as it is today.
  2. self_cpu_time: int = e.self_cpu_time_total -- this is new but is already more than 10x less CPU-intensive than 1.
  3. handle.push_frame(parent.name, _FILE_PLACEHOLDER, 0, 0) -- this is not new and not called more often than it used to be
  4. handle.flush_sample() -- this is not new and not called more often than it used to be
  5. handle.push_frame(f"PYTORCH_{device_type_str}", _DEVICE_FRAME_FILE_NAME, 0, 0) -- this is not new and not called more often than it used to be

In short, the most resource-hungry functions on the PyTorch Profiler at the moment are functions that already were there before, and that already were CPU-hungry before.
The added logic (to reconstruct the tree) is practically irrelevant as far as performance goes.

Related work

New prof-correctness check: DataDog/prof-correctness#152

@datadog-datadog-prod-us1-2

This comment has been minimized.

@pr-commenter

pr-commenter Bot commented Jun 4, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-06-04 09:21:10

Comparing candidate commit 762fc42 in PR branch kowalski/feat-profiling-real-torch-flame-graphs with baseline commit fa666d3 in branch main.

Found 0 performance improvements and 5 performance regressions! Performance is the same for 615 metrics, 10 unstable metrics.

scenario:iastaspects-index_aspect

  • 🟥 execution_time [+16.698µs; +21.747µs] or [+13.190%; +17.177%]

scenario:iastaspects-title_aspect

  • 🟥 execution_time [+51.426µs; +56.131µs] or [+15.564%; +16.988%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+108.981µs; +117.091µs] or [+25.305%; +27.188%]

scenario:span-start

  • 🟥 execution_time [+1.154ms; +1.340ms] or [+7.412%; +8.601%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+162.537ns; +199.028ns] or [+7.652%; +9.370%]

@KowalskiThomas KowalskiThomas added the Profiling Continous Profling label Jun 4, 2026
@KowalskiThomas
KowalskiThomas force-pushed the kowalski/feat-profiling-real-torch-flame-graphs branch from 762fc42 to 1e5f2f8 Compare June 4, 2026 10:06
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codeowners resolved as

.github/workflows/pytorch_gpu_tests.yml                                 @DataDog/profiling-python
ddtrace/internal/settings/_supported_configurations.py                  @DataDog/apm-sdk-capabilities-python @DataDog/apm-python
ddtrace/internal/settings/profiling.py                                  @DataDog/profiling-python
ddtrace/internal/settings/profiling.pyi                                 @DataDog/profiling-python
ddtrace/profiling/collector/pytorch.py                                  @DataDog/profiling-python
releasenotes/notes/profiling-improve-pytorch-flame-graphs-6e0d84ac06f4f797.yaml  @DataDog/apm-python
supported-configurations.json                                           @DataDog/apm-sdk-capabilities-python @DataDog/apm-python
tests/profiling/simple_program_pytorch_cpu.py                           @DataDog/profiling-python
tests/profiling/test_pytorch.py                                         @DataDog/profiling-python
tests/telemetry/test_writer.py                                          @DataDog/apm-python

@KowalskiThomas
KowalskiThomas force-pushed the kowalski/feat-profiling-real-torch-flame-graphs branch 3 times, most recently from f2928af to 2a99ff9 Compare June 4, 2026 14:11
@KowalskiThomas
KowalskiThomas marked this pull request as ready for review June 5, 2026 09:07
@KowalskiThomas
KowalskiThomas requested review from a team as code owners June 5, 2026 09:07

@emmettbutler emmettbutler left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

release note looks good

@vlad-scherbich

Copy link
Copy Markdown
Contributor

Note this theoretically comes at a performance cost,

@KowalskiThomas :
Does it make sense to add a DoE benchmark just for PyTorch profiler, or would that already be captured by existing ones?

@KowalskiThomas

Copy link
Copy Markdown
Collaborator Author

Does it make sense to add a DoE benchmark just for PyTorch profiler, or would that already be captured by existing ones?

Given that we don't officially support PyTorch in the Profiler (it's "experimental") I don't think adding a DoE benchmark for it is warranted at this point. We also probably don't really know what a typical workload would look like so it wouldn't be straightforward to do so.

@vlad-scherbich vlad-scherbich left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Comment thread ddtrace/profiling/collector/pytorch.py Outdated
@KowalskiThomas
KowalskiThomas force-pushed the kowalski/feat-profiling-real-torch-flame-graphs branch from 1af3ae4 to f206ee1 Compare June 12, 2026 13:38
@KowalskiThomas
KowalskiThomas requested a review from a team as a code owner June 12, 2026 14:19
@KowalskiThomas
KowalskiThomas requested a review from mabdinur June 12, 2026 14:19
@KowalskiThomas
KowalskiThomas force-pushed the kowalski/feat-profiling-real-torch-flame-graphs branch from 795a8d3 to 50f1c9d Compare June 12, 2026 14:45
@KowalskiThomas
KowalskiThomas force-pushed the kowalski/feat-profiling-real-torch-flame-graphs branch from 50f1c9d to 35c1f5e Compare June 12, 2026 15:20
@KowalskiThomas

Copy link
Copy Markdown
Collaborator Author

/remove

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Jun 12, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-06-12 15:46:48 UTC ℹ️ Start processing command /remove


2026-06-12 15:46:51 UTC ℹ️ Devflow: /remove

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 6f0bd0f into main Jun 12, 2026
463 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the kowalski/feat-profiling-real-torch-flame-graphs branch June 12, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Profiling Continous Profling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants